#include <firebird/boost/win_file_device.hpp>
#include <boost/iostreams/detail/error.hpp>
#include <boost/iostreams/detail/ios.hpp>  // openmodes, failure.
#include <boost/integer_traits.hpp>

// OS-specific headers for low-level i/o.

#include <cassert>
#include <cstdio>        // SEEK_SET, etc.
#include <errno.h>
#include <fcntl.h>       // file opening flags.
#include <sys/stat.h>    // file access permissions.
# include <io.h>         // low-level file i/o.
#define WINDOWS_LEAN_AND_MEAN
#include <windows.h>

namespace boost { namespace iostreams {

void win_file_device::open
( const std::string& path, std::ios::openmode m,
 std::ios::openmode base )
{
	using namespace std;
	m |= base;
	DWORD dwDesiredAccess;
	DWORD dwCreationDisposition;
	DWORD dwShareMode;

	if ( (m & (std::ios::in | std::ios::out))
		==
		(std::ios::in | std::ios::out) )
	{
		assert(!(m & std::ios::app));
		dwDesiredAccess = GENERIC_READ | GENERIC_WRITE;
		dwCreationDisposition =
			(m & std::ios::trunc) ?
OPEN_ALWAYS :
		OPEN_EXISTING;
	} else if (m & std::ios::in) {
		assert(!(m & (std::ios::app |std::ios::trunc)));
		dwDesiredAccess = GENERIC_READ;
		dwCreationDisposition = OPEN_EXISTING;
		dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE;
	} else if (m & std::ios::out) {
		dwDesiredAccess = GENERIC_WRITE;
		dwCreationDisposition = OPEN_ALWAYS;
		dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE;
		if (m & std::ios::app)
			pimpl_->flags_ |= impl::append;
	}

	HANDLE handle =
		::CreateFile( path.c_str(),
		dwDesiredAccess,
		dwShareMode,
		NULL,                   // lpSecurityAttributes
		dwCreationDisposition,
		FILE_ATTRIBUTE_NORMAL,
		NULL );                 // hTemplateFile
	if (handle != INVALID_HANDLE_VALUE) {
		pimpl_->handle_ = handle;
		pimpl_->flags_ |= impl::close_on_exit | impl::has_handle;
	} else {
		pimpl_->flags_ = 0;
		throw std::ios::failure("bad open");
	}
}

std::streamsize win_file_device::read(char_type* s, std::streamsize n)
{
	DWORD result;
	if (!::ReadFile(pimpl_->handle_, s, n, &result, NULL))
		throw detail::bad_read();
	return static_cast<std::streamsize>(result);
}

std::streamsize win_file_device::write(const char_type* s, std::streamsize n)
{

	if (pimpl_->flags_ & impl::append) {
		::SetFilePointer(pimpl_->handle_, 0, NULL, FILE_END);
		if (::GetLastError() != NO_ERROR)
			throw detail::bad_seek();
	}
	DWORD ignore;
	if (!::WriteFile(pimpl_->handle_, s, n, &ignore, NULL))
		throw detail::bad_write();
	return n;

}

std::streampos win_file_device::seek(stream_offset off, std::ios::seekdir way)
{
	using namespace std;
	LONG lDistanceToMove = static_cast<LONG>(off & 0xffffffff);
	LONG lDistanceToMoveHigh = static_cast<LONG>(off >> 32);
	DWORD dwResultLow =
		::SetFilePointer( pimpl_->handle_,
		lDistanceToMove,
		&lDistanceToMoveHigh,
		way == std::ios::beg ? FILE_BEGIN : way == std::ios::cur ? FILE_CURRENT : FILE_END );
	if (::GetLastError() != NO_ERROR) {
		throw detail::bad_seek();
	} else {
		return offset_to_position((lDistanceToMoveHigh << 32) + dwResultLow);
	}
}

void win_file_device::close() { close_impl(*pimpl_); }

void win_file_device::close_impl(impl& i)
{
	if (i.flags_ & impl::has_handle) {
		if (!::CloseHandle(i.handle_))
			throw std::ios::failure("bad close");
		i.fd_ = -1;
		i.flags_ = 0;
		return;
	}
}
} }